#include "CertHelper.h"
#include <openssl\pem.h>
#include <openssl\err.h>

LIB_UTIL_NAMESPACE_BEGIN

class CertHelperOpenSSLImpl : public ICertHelper
{
public:
	CertHelperOpenSSLImpl();
	virtual ~CertHelperOpenSSLImpl();

	virtual bool InitVerify(const string& keyPath = "", const tDataBuffer* keyData = NULL);
	virtual bool VerifyUpdate(const tDataBuffer& data);
	virtual bool Verfity(const tDataBuffer& signature, bool isBase64);

	//if keyData is null, use keyPath to load cert or else use keyData first.
	//if keyPath is empty, use keyData to load cert.
	//if both empty, return false.
	virtual bool InitSign(const string& password, const string& keyPath = "", const tDataBuffer* keyData = NULL);// { return false; }
	virtual bool SignUpdate(const tDataBuffer& data);// { return false; } { return false; }
	virtual bool Sign(tDataBuffer& signature, bool isBase64);// { return false; } { return false; }

	virtual void Release();

private:
	EVP_PKEY* m_key;
	EVP_MD_CTX* m_md_ctx;
};

CertHelperOpenSSLImpl::CertHelperOpenSSLImpl() 
{ 
	OpenSSL_add_all_algorithms();
	m_key = NULL;  
	m_md_ctx = NULL; 
}

CertHelperOpenSSLImpl::~CertHelperOpenSSLImpl() 
{ 
	Release();
}

void CertHelperOpenSSLImpl::Release()
{
	if(m_key)
		EVP_PKEY_free(m_key); 
	m_key = NULL;
	if(m_md_ctx)
	{
		EVP_MD_CTX_cleanup(m_md_ctx);
		delete m_md_ctx;
	}
	m_md_ctx = NULL;
}

bool CertHelperOpenSSLImpl::InitVerify(const string& keyPath, const tDataBuffer* keyData)
{
	if(m_key || m_md_ctx)
		Release();

	BIO* bio = BIO_new_file(keyPath.c_str(), "r");
	if(!bio)
		return false;

	m_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
	if(!m_key)
		return false;
	m_md_ctx = new EVP_MD_CTX;
	bool ret = EVP_VerifyInit(m_md_ctx, EVP_sha1());
	BIO_free(bio);
	return ret;
}

bool CertHelperOpenSSLImpl::VerifyUpdate(const tDataBuffer& data)
{
	if(!m_key || !m_md_ctx)
		return false;
	return EVP_VerifyUpdate(m_md_ctx, data.data, data.size);
}

bool CertHelperOpenSSLImpl::Verfity(const tDataBuffer& signature, bool isBase64)
{
	if(!m_key || !m_md_ctx)
		return false;

	bool ret = false;
	if(isBase64) 
	{
		EncryptHelper* encrypt = EncryptHelper::CreateEncryptHelper();
		tDataBuffer out;
		encrypt->DecryptData(signature, out, EncryptHelper::Base64NoeEncrypt);
		ret = EVP_VerifyFinal(m_md_ctx, out.data, out.size, m_key);
		EncryptHelper::ReleaseEncryptHelper(encrypt);
	}
	else
		ret = EVP_VerifyFinal(m_md_ctx, signature.data, signature.size, m_key);
	return ret;
}

bool CertHelperOpenSSLImpl::InitSign(const string& password, const string& keyPath, const tDataBuffer* keyData) 
{ 
	if(m_key || m_md_ctx)
		Release();

	BIO* bio = BIO_new_file(keyPath.c_str(), "r");
	if(!bio)
		return false;

	m_key = PEM_read_bio_PrivateKey(bio, NULL, NULL, (void*)password.c_str());
	if(!m_key)
		return false;
	m_md_ctx = new EVP_MD_CTX;
	bool ret = EVP_SignInit(m_md_ctx, EVP_sha1());
	BIO_free(bio);
	return ret;
}

bool CertHelperOpenSSLImpl::SignUpdate(const tDataBuffer& data) 
{ 
	if(!m_key || !m_md_ctx)
		return false;
	return EVP_SignUpdate(m_md_ctx, data.data, data.size);
}

bool CertHelperOpenSSLImpl::Sign(tDataBuffer& signature, bool isBase64) 
{
	if(!m_key || !m_md_ctx)
		return false;

	byte buffer[256];
	unsigned int size;
	bool ret = EVP_SignFinal(m_md_ctx, buffer, &size, m_key);
	if(!ret)
		return false;

	if(isBase64)
	{
		EncryptHelper* encrypt = EncryptHelper::CreateEncryptHelper();
		encrypt->EncryptData(tDataBuffer(buffer, size, true), signature, EncryptHelper::Base64NoeEncrypt);
		EncryptHelper::ReleaseEncryptHelper(encrypt);
	}
	else
	{
		signature.data = new byte[256];
		signature.size = size;
		memcpy(signature.data, buffer, 256);
	}
	return ret;
}

ICertHelper* ICertHelper::CreateCertHelper()
{
	return new CertHelperOpenSSLImpl;
}

void ICertHelper::ReleaseCertHelper(ICertHelper* certHelper)
{
	if(certHelper)
		delete certHelper;
}

LIB_UTIL_NAMESPACE_END